<template >
<div class="vue-waterfall-easy"
  :style="isMobile? {height: calHeight ? wrapperHeight + 'px' : ''}:{width:colWidth*columnCount+'px',left:'50%',marginLeft: -1*colWidth*columnCount/2 +'px'}">
  <a class="img-box"
    v-for="(v,i) in imgsArrC"
    :key="i"
    @click="clickItem(v, i)"
    :style="{padding:gap/2+'px',width: isMobile ? '' : colWidth+'px'}">
    <div class="img-inner-box">
      <div class="img-wraper" :style="{width:imgWidthC+'px',height:v.height?v.height+'px':''}" v-lazy:backgroundImage="v.src">
        <div class="img-content">
          <slot name="content" :index="i" :value="v">
          </slot>
        </div>
      </div>
      <div class="img-info">
        <slot name="bottom" :index="i" :value="v">
        </slot>
      </div>
    </div>
  </a>
  <div v-if="isPreloadingC" class="loaing-wrapper">
    <div class="bounce-loading">
      <div class="double-bounce1"></div>
      <div class="double-bounce2"></div>
      <p>{{loadingTip}}</p>
    </div>
  </div>
</div>
</template>

<script>
export default {
  name: 'vue-waterfall-easy',
  props: {
    gap: {
      type: Number,
      default: 20
    },
    maxCols: {
      type: Number,
      default: 5
    },
    imgsArr: {
      type: Array,
      required: true
    },
    imgWidth: {
      type: Number,
      default: 240
    },
    timeOut: { // 预加载事件小于500毫秒就不显示加载动画，增加用户体验
      type: Number,
      default: 500
    },
    customWidth: {
      type: Number,   // 自定义一项的宽度，会覆盖后面计算出的imgWidthC
      default: null
    },
    calHeight: {
      type: Boolean,    // 是否需要计算出高度
      default: false
    },
    wrapperName: {  // 容器ID
      type: String,
      default: null
    },
    imgMaxHeight: {
      type: Number,
      default: null
    },
    loadingTip: {
      type: String,
      default: '正在加载图片...'
    }
  },
  data () {
    return {
      msg: 'this is from vue-waterfall-easy.vue',
      columnCount: NaN, // 列数，根据窗口大小初始化
      isMobile: navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i), // 初始化移动端
      beginIndex: NaN, // 第二列首张图片的index，从这一张开始重新计算图片位置
      colsHeightArr: [], // 每一列的图片总和高度为元素组成的数组
      imgBoxEls: null, // 所有的.img-box元素
      isPreloading: true, // 预加载状态中（1.以等待图片替换 2.图片全部预加载完显示）
      isPreloadingC: true,
      imgsArrC: [], // 预加载完之后再才开始
      loadedCount: 0, // 已经加载图片数量
      wrapperHeight: 0
    }
  },
  computed: {
    colWidth () { // 每一列的宽度
      return this.imgWidth + this.gap
    },
    imgWidthC () { // 对于移动端重新计算图片宽度
      if (this.customWidth) {
        return this.customWidth
      }
      return this.isMobile ? window.innerWidth / 2 - this.gap : this.imgWidth
    }
  },
  activated () {
    // 处理在网络加载image时，页面跳转导致img-box错位。重新布局
    if (this.colsHeightArr.length > 0 && this.colsHeightArr[0] === 0) {
      this.beginIndex = this.columnCount // 开始排列的元素索引
      this.initColsHeightArr()
      this.waterfall()
    }
  },
  methods: {
    waterfall () { // 执行瀑布布局
      for (var i = this.beginIndex; i < this.imgsArr.length; i++) {
        var minHeight = Math.min.apply(null, this.colsHeightArr) // 最低高低
        var minIndex = this.colsHeightArr.indexOf(minHeight) // 最低高度的索引
        var width = this.imgBoxEls[0].offsetWidth // 图片的宽度获取
        // 设置元素定位的位置
        this.imgBoxEls[i].style.position = 'absolute'
        this.imgBoxEls[i].style.left = minIndex * width + 'px'
        this.imgBoxEls[i].style.top = minHeight + 'px'
        // 更新colsHeightArr
        this.$set(this.colsHeightArr, minIndex, minHeight + this.imgBoxEls[i].offsetHeight)
        if (this.calHeight && i >= this.imgsArr.length - 2) {
          this.wrapperHeight = Math.max(this.wrapperHeight, Math.max.apply(null, this.colsHeightArr))
        }
      }
      this.beginIndex = this.imgsArr.length
    },
    loadFn (e, oImg, i) { // 每张图片预加载完成执行函数
      this.loadedCount++
      if (e.type === 'load') { // 使用图片原始宽度计算图片的高度
        let height = Math.round(this.imgWidthC / (oImg.width / oImg.height))
        if (this.imgMaxHeight && height > this.imgMaxHeight) {
          height = this.imgMaxHeight
        }
        this.$set(this.imgsArr[i], 'height', height)
      } else {
        // 加载失败时，使用加载失败的图片
        this.$set(this.imgsArr[i], 'height', 80)
      }
      if (this.loadedCount === this.imgsArr.length) {
        this.imgsArrC = this.imgsArr.concat([])
        this.isPreloading = false
        // 预加载完毕
        this.$nextTick(() => {
          this.initImgBoxEls()
          this.$emit('preloaded')
        })
      }
    },
    preload () {
      this.imgsArr.forEach((v, i) => {
        if (i < this.loadedCount) return
        var oImg = new Image()
        oImg.addEventListener('load', (e) => {
          this.loadFn(e, oImg, i)
        })
        oImg.addEventListener('error', (e) => {
          this.loadFn(e, oImg, i)
        })
        oImg.src = v.src
      })
    },
    // -----------------初始化化------------------------
    initColsHeightArr () { // 第一行元素的高度组成的数组-初始化
      this.colsHeightArr = [] // 列数发生变化重新初始化
      for (var i = 0; i < this.columnCount; i++) {
        this.imgBoxEls[i].style.position = 'static' // 重置下position
        var height = this.imgBoxEls[i].offsetHeight
        this.colsHeightArr.push(height)
      }
    },
    initImgBoxEls () {
      // 初始化所有装图片的元素集合,注意高度获取需要在图片加载完成之后，所以在window.onload 事件中初始化
      // if (this.wrapperName) {
      //   if (document.getElementById(this.wrapperName)) {
      //     this.imgBoxEls = document.getElementById(this.wrapperName).getElementsByClassName('img-box')
      //   }
      // } else {
      this.imgBoxEls = this.$el.getElementsByClassName('img-box')
      // }
    },
    initColumnCount () { // 列数初始化
      var winWidth = window.innerWidth
      var columnCount = parseInt(winWidth / this.colWidth)
      columnCount = columnCount === 0 ? 1 : columnCount
      this.columnCount = this.isMobile
        ? 2
        : (columnCount > this.maxCols ? this.maxCols : columnCount)
    },
    clickItem (item, index) {
      this.$emit('clickItem', item, index)
    }
  },
  mounted () {
    // ==1== 根据窗口大小初始化列数
    this.initColumnCount()
    this.beginIndex = this.columnCount // 开始排列的元素索引
    // ==2== 根据预加载完成的图片的长宽比，计算图片的高度
    this.preload()
    this.$on('preloaded', () => {
      if (this.colsHeightArr.length === 0) this.initColsHeightArr() // 第一次初始化
      this.waterfall()
    })
    window.addEventListener('resize', () => {
      var old = this.columnCount
      this.initColumnCount()
      if (old === this.columnCount) return // 列数不变直接退出
      this.beginIndex = this.columnCount // 开始排列的元素索引
      this.initColsHeightArr()
      this.waterfall()
    })
    this.$el.parentNode.addEventListener('scroll', () => {
      if (this.isPreloading) return
      const lastImgHeight = this.imgsArr[this.imgsArr.length - 1].height
      if ((this.$el.parentNode.scrollTop + this.$el.parentNode.offsetHeight) > (this.$el.parentNode.scrollHeight - lastImgHeight)) {
        this.$emit('scrollLoadImg')
      }
    })
  },
  watch: {
    imgsArr (newV, oldV) {
      if (newV.length > 0 && newV.length === oldV.length && newV[0].src === oldV[0].src) return
      this.isPreloading = true // 预加载新的图片资源
      this.loadedCount = 0
      this.beginIndex = this.columnCount // 开始排列的元素索引
      this.colsHeightArr = []
      this.wrapperHeight = 0
      this.preload()
    },
    isPreloading (v) {
      if (v) {
        setTimeout(() => {
          if (!this.isPreloading) return // 500毫秒内预加载完图片则不显示加载动画
          this.isPreloadingC = true
        }, this.timeOut)
      } else {
        this.isPreloadingC = false
      }
    }
  }
}
</script>

<style lang="stylus">
.vue-waterfall-easy {
  position: relative;
  width: 100%; // 移动端生效

  .img-box {
    display: inline-block;
    width: 50%; // 移动端生效
    box-sizing: border-box;
    float: left; // 首行排版
    transition: left 1s, top 1s;

    .img-inner-box {
      box-shadow: 0 1px 5px #d2cece;

      .img-wraper {
        background-size: cover;
        background-position: 50% 50%;
        width: 100%;
      }

      .img-info {
        background: #fff;
        // padding: 0.6em;
      }
    }
  }

  .loaing-wrapper {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -100%);
    z-index: 999
  }

  .bounce-loading {
    width 120px
    height 95px
    text-align center
    position relative
    .double-bounce1, .double-bounce2 {
      position absolute
      left 30px
      display inline-block
      width: 60px;
      height: 60px;
      border-radius: 50%;
      background-color: #67CF22;
      opacity: 0.6;
      animation: bounce 2s infinite ease-in-out;
    }

    .double-bounce2 {
      animation-delay: -1s;
    }
    p {
      position absolute
      bottom 0
      left 0
      right 0
      font-size 13px
      color #666
    }
  }

  @keyframes bounce {
    0%, 100% {
      transform: scale(0);
    }

    50% {
      transform: scale(1);
    }
  }
}
</style>
